package com.kexilo.core.common.utils;

import com.kexilo.core.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * 文件上传工具类
 * 
 * @author Kexilo
 */
@Slf4j
public class FileUploadUtils {
    
    /**
     * 默认大小 50MB
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
    
    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
    
    /**
     * 默认上传的地址
     */
    private static String defaultBaseDir = System.getProperty("user.dir") + "/profile";
    
    /**
     * 图片文件扩展名
     */
    private static final List<String> IMAGE_EXTENSION = Arrays.asList("bmp", "gif", "jpg", "jpeg", "png", "webp", "svg");
    
    /**
     * 文档文件扩展名
     */
    private static final List<String> DOCUMENT_EXTENSION = Arrays.asList("txt", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf");
    
    /**
     * 压缩文件扩展名
     */
    private static final List<String> ARCHIVE_EXTENSION = Arrays.asList("zip", "rar", "7z", "tar", "gz");
    
    /**
     * 允许的文件扩展名
     */
    private static final List<String> ALLOWED_EXTENSION = Arrays.asList(
        // 图片
        "bmp", "gif", "jpg", "jpeg", "png", "webp", "svg",
        // 文档
        "txt", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf",
        // 压缩文件
        "zip", "rar", "7z", "tar", "gz",
        // 音频
        "mp3", "wav", "ogg", "flac",
        // 视频
        "mp4", "avi", "mov", "wmv", "flv", "mkv"
    );
    
    public static void setDefaultBaseDir(String defaultBaseDir) {
        FileUploadUtils.defaultBaseDir = defaultBaseDir;
    }
    
    public static String getDefaultBaseDir() {
        return defaultBaseDir;
    }
    
    /**
     * 以默认配置进行文件上传
     *
     * @param file 上传的文件
     * @return 文件名称
     * @throws Exception
     */
    public static final String upload(MultipartFile file) throws IOException {
        try {
            return upload(getDefaultBaseDir(), file, ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }
    
    /**
     * 根据文件路径上传
     *
     * @param baseDir 相对应用的基目录
     * @param file    上传的文件
     * @return 文件名称
     * @throws IOException
     */
    public static final String upload(String baseDir, MultipartFile file) throws IOException {
        try {
            return upload(baseDir, file, ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }
    
    /**
     * 文件上传
     *
     * @param baseDir          相对应用的基目录
     * @param file             上传的文件
     * @param allowedExtension 上传文件类型
     * @return 返回上传成功的文件名
     * @throws ServiceException 比如读写文件出错时
     * @throws IOException      比如读写文件出错时
     */
    public static final String upload(String baseDir, MultipartFile file, List<String> allowedExtension)
            throws ServiceException, IOException {
        int fileNamelength = file.getOriginalFilename().length();
        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new ServiceException("文件名长度不能超过" + FileUploadUtils.DEFAULT_FILE_NAME_LENGTH + "个字符");
        }
        
        assertAllowed(file, allowedExtension);
        
        String fileName = extractFilename(file);
        
        File desc = getAbsoluteFile(baseDir, fileName);
        file.transferTo(desc);
        String pathFileName = getPathFileName(baseDir, fileName);
        return pathFileName;
    }
    
    /**
     * 编码文件名
     */
    public static final String extractFilename(MultipartFile file) {
        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        fileName = DateUtils.datePath() + "/" + UUID.randomUUID().toString() + "." + extension;
        return fileName;
    }
    
    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {
        File desc = new File(uploadDir + File.separator + fileName);
        
        if (!desc.exists()) {
            if (!desc.getParentFile().exists()) {
                desc.getParentFile().mkdirs();
            }
        }
        return desc;
    }
    
    private static final String getPathFileName(String uploadDir, String fileName) throws IOException {
        int dirLastIndex = getDefaultBaseDir().length() + 1;
        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
        String pathFileName = "/" + currentDir + "/" + fileName;
        return pathFileName;
    }
    
    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @return
     * @throws ServiceException 文件大小超出最大值
     */
    public static final void assertAllowed(MultipartFile file, List<String> allowedExtension)
            throws ServiceException {
        long size = file.getSize();
        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE) {
            throw new ServiceException("文件大小超出限制的" + (DEFAULT_MAX_SIZE / 1024 / 1024) + "MB");
        }
        
        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
            if (allowedExtension == IMAGE_EXTENSION) {
                throw new ServiceException("文件格式不正确，请上传图片类型,如：" + allowedExtension.toString());
            } else if (allowedExtension == DOCUMENT_EXTENSION) {
                throw new ServiceException("文件格式不正确，请上传文档类型,如：" + allowedExtension.toString());
            } else if (allowedExtension == ARCHIVE_EXTENSION) {
                throw new ServiceException("文件格式不正确，请上传压缩类型,如：" + allowedExtension.toString());
            } else {
                throw new ServiceException("文件格式不正确，请上传" + allowedExtension.toString() + "格式");
            }
        }
    }
    
    /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension
     * @param allowedExtension
     * @return
     */
    public static final boolean isAllowedExtension(String extension, List<String> allowedExtension) {
        for (String str : allowedExtension) {
            if (str.equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 获取文件名的后缀
     *
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (StringUtils.isEmpty(extension)) {
            extension = getExtensionByContentType(file.getContentType());
        }
        return extension;
    }
    
    /**
     * 根据文件的ContentType获取扩展名
     * 
     * @param contentType 文件的ContentType
     * @return 扩展名
     */
    private static String getExtensionByContentType(String contentType) {
        if (StringUtils.isEmpty(contentType)) {
            return "";
        }
        
        switch (contentType.toLowerCase()) {
            case "image/jpeg":
                return "jpg";
            case "image/png":
                return "png";
            case "image/gif":
                return "gif";
            case "image/bmp":
                return "bmp";
            case "image/webp":
                return "webp";
            case "image/svg+xml":
                return "svg";
            case "application/pdf":
                return "pdf";
            case "application/msword":
                return "doc";
            case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
                return "docx";
            case "application/vnd.ms-excel":
                return "xls";
            case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
                return "xlsx";
            case "application/zip":
                return "zip";
            case "application/x-rar-compressed":
                return "rar";
            default:
                return "";
        }
    }
    
    /**
     * 删除文件
     * 
     * @param filePath 文件路径
     * @return 是否删除成功
     */
    public static boolean deleteFile(String filePath) {
        boolean flag = false;
        File file = new File(filePath);
        // 路径为文件且不为空则进行删除
        if (file.isFile() && file.exists()) {
            flag = file.delete();
        }
        return flag;
    }
    
    /**
     * 检查文件是否为图片
     * 
     * @param extension 文件扩展名
     * @return 是否为图片
     */
    public static boolean isImage(String extension) {
        return IMAGE_EXTENSION.contains(extension.toLowerCase());
    }
    
    /**
     * 检查文件是否为文档
     * 
     * @param extension 文件扩展名
     * @return 是否为文档
     */
    public static boolean isDocument(String extension) {
        return DOCUMENT_EXTENSION.contains(extension.toLowerCase());
    }
    
    /**
     * 检查文件是否为压缩文件
     * 
     * @param extension 文件扩展名
     * @return 是否为压缩文件
     */
    public static boolean isArchive(String extension) {
        return ARCHIVE_EXTENSION.contains(extension.toLowerCase());
    }
}
